import { Link, Warning } from '@brillout/docpress'

Telefunc's approach — seamlessly calling remote functions — is known as [RPC (**R**emote **P**rocedure **C**all)](https://en.wikipedia.org/wiki/Remote_procedure_call).

This page explains:
- What is RPC?
- How does RPC work?
- How to enforce RPC security?
- How to maximize RPC performance?

## Basic example

Telefunc enables functions defined on the server-side to be called remotely from the browser-side.

```js
// hello.telefunc.js
// Environment: server

export { hello }

// hello() always runs on the server-side
async function hello({ name }) {
  const message = 'Welcome ' + name
  return { message }
}
```

```html
<!-- index.html -->
<!-- Environment: client -->

<html>
  <body>
    <script type="module">
      // This import doesn't actually load the hello.telefunc.js file: Telefunc transforms
      // hello.telefunc.js into a thin HTTP client.
      import { hello } from './hello.telefunc.js'
      // This thin HTTP client makes an HTTP request when the hello() function is called.
      const { message } = await hello({ name: 'Eva' })
      console.log(message) // Prints 'Welcome Eva'
    </script>
  </body>
</html>
```

The central aspect here is that `hello()` is always executed on the server side, which enables `hello()` to use server-side utilities such as SQL and ORMs.


## ORM & SQL

As we have seen in the previous section, telefunctions always run on the server-side and can therefore use server utilities such as SQL and ORMs.

```js
// TodoList.telefunc.js
// Environment: server

export { onLoad }

async function onLoad() {
  // ORM
  const todoItems = await Task.findMany({ select: 'text' })
  // SQL
  const todoItems = await execute("SELECT text FROM todo_items;")

  return todoItems
}
```

> `.telefunc.js` files are guaranteed to be loaded only on the server-side. You can therefore save secret information, such as the database passwords, in `.telefunc.js` files.

```jsx
// TodoList.jsx
// Environment: client

// This doesn't actually load CreateTodo.telefunc.js which we'll explain in the next section
import { onLoad } from './TodoList.telefunc.js'

async function TodoList() {
  // The frontend uses the telefunction onLoad() to retrieve data by executing a SQL/ORM query
  const todoItems = await onLoad()
  return (
    <ul>
      {todoItems.map((item) => (
        <li>{item.text}</li>
      ))}
    </ul>
  )
}
```

> We name and co-locate `TodoList.telefunc.js` next to `TodoList.jsx` — a practice explained at <Link href="/event-based" />.

> While the examples here use JSX, Telefunc works with any UI framework (React, Vue, Svelte, Solid, ...).

Telefunctions can also be used to mutate data:

```js
// CreateTodo.telefunc.js
// Environment: server

export { onNewTodo }

import { shield } from 'telefunc'

// We'll talk about shield() later
shield(onNewTodo, [shield.type.string])
async function onNewTodo(text) {
  // ORM
  const todoItem = new Task({ text })
  await todoItem.save()

  // SQL
  await execute('INSERT INTO todo_items VALUES (:text)', { text })
}
```

```jsx
// CreateTodo.jsx
// Environment: client

import { onNewTodo } from './CreateTodo.telefunc.js'

async function onClick(form) {
  const text = form.input.value
  await onNewTodo(text)
}

function CreateTodo() {
  return (
    <form>
      <input input="text"></input>
      <button onClick={onClick}>Add To-Do</button>
    </form>
  )
}
```

RPC enables your frontend to tap directly into the full power of the server such as SQL and ORMs. For most use cases it's simpler, more flexible, and more performant than REST and GraphQL.

> GraphQL and RESTful can be better than RPC if:
> - you want to give third parties generic access to your data, or
> - you are a very large company with highly complex databases.
>
> See <Link href="/RPC-vs-GraphQL-REST" />.


## How it works

> Understanding the basic mechanics of Telefunc is paramount in order to proficiently use it.

Let's see what happens when a telefunction is called.

```js
// hello.telefunc.js
// Environment: server

export { hello }

async function hello({ name }) {
  const message = 'Welcome ' + name
  return { message }
}
```

```js
// Environment: client

import { hello } from './hello.telefunc.js'

const { message } = await hello('Eva')
```

The `hello.telefunc.js` file is never loaded in the browser: instead Telefunc transforms `hello.telefunc.js` into the following:

```js
// hello.telefunc.js (after Telefunc transformation)
// Environment: Browser
import { __internal_makeHttpRequest } from 'telefunc/client'
export const hello = (...args) => __internal_makeHttpRequest('/hello.telefunc.js:hello', args)
```

When `hello('Eva')` is called in the browser-side, the following happens:
 1. On the browser-side, the `__internal_makeHttpRequest()` function makes an HTTP request to the server.
    ```
    POST /_telefunc HTTP/1.1
    {
      "path": "/hello.telefunc.js:hello",
      "args": [{"name": "Eva"}]
    }
    ```
 2. On the server-side, the Telefunc middleware:
    ```js
    // server.js

    // Server (Express.js/Hono/Fastify/...)

    import { telefunc } from 'telefunc'

    // Telefunc middleware
    app.use('/_telefunc', async (req: Request, res: Response) => {
      const httpResponse = await telefunc(req)
      res.send(httpResponse.body)
    })
    ```
    Replies following HTTP response:
    ```
    HTTP/1.1 200 OK
    {
      "return": {
        "message": "Welcome Eva"
      }
    }
    ```

In other words,
the `hello()` function is always executed on the server-side
while the browser-side can remotely call it in a seamless fashion.

> You can also call telefunctions from the server-side, in which case the telefunction is directly called (without making an HTTP request).


## Security

Anyone — not just your frontend — can remotely call your telefunctions.

For example, anyone can call the `hello()` telefunction we've seen in the previous section by opening a Linux terminal and making this HTTP request:

```bash
curl https://your-website.com/_telefunc --data '{
   "path": "/hello.telefunc.js:hello",
   "args": [{"name": "Elisabeth"}]
 }'
```

Thus, such telefunction is problematic:

```js
// sql.telefunc.js
// Environment: server

// run() is public: it can be called by anyone
export { run }

async function run(sql) {
  return await database.execute(sql)
}
```

This `run()` telefunction essentially exposes the entire database to the world as
anyone can make this HTTP request:

```bash
curl https://your-website.com/_telefunc --data '{
    "path": "/run.telefunc.js:run",
    "args": ["SELECT login, password FROM users;"]
  }'
```

Always keep in mind that your **telefunctions are public** and you must **always protect your telefunctions**.

In the sections below <Link href="#throw-abort"/> and <Link href="#shield" /> we explain how to achieve that.


## `throw Abort()`

As we've seen in the previous section,
the following telefunction isn't safe.

```js
// run.telefunc.js
// Environment: server

// run() is public: it can be called by anyone
export { run }

async function run(sql) {
  return await database.execute(sql)
}
```

But we can use `throw Abort()` to protect it:

```js
// run.telefunc.js
// Environment: server

// run() is public: it can be called by anyone
export { run }

import { Abort, getContext } from 'telefunc'

async function run(sql) {
  const { user } = getContext()

  // Only admins are allowed to run this telefunction
  if (user.isAdmin !== true) throw Abort()

  return await database.execute(sql)
}
```

> Telefunctions can access contextual information by using <Link href="/getContext">`getContext()`</Link>.

We can use `throw Abort()` to avoid any forbidden telefunction call.

```js
// TodoList.telefunc.js
// Environment: server

export { onLoad }

import { Abort, getContext } from 'telefunc'

async function onLoad() {
  const { user } = getContext()

  // We forbid onLoad() to be called by a user that isn't logged-in
  if (!user) throw Abort()

  const todoList = await Task.findMany({ authorId: user.id })
  return todoList
}
```

> We essentially use `throw Abort()` to implement permission: only a logged-in user is allowed to fetch its to-do items.
> We talk more about permissions at <Link href="/permissions" />.

> In principle, we could also `throw new Error()` instead of `throw Abort()` as it also interrupts the telefunction call. But we recommend `throw Abort()` as it comes with many conveniences.

> If, upon aborting a telefunction call, you want to pass information to the frontend then use `return someValue` or `throw Abort(someValue)`, see <Link href="/permissions" />.


## `shield()`

Since telefunctions are public and can be called by anyone, we cannot assuming anything about arguments. We can use `throw Abort()` to ensure the type of telefunction arguments:

```js
// CreateTodo.telefunc.js
// Environment: server

export async function onNewTodo(text) {
  // ❌ This may throw:
  // ```
  // Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase')
  // ```
  // While the frontend may always call onNewTodo(text) with `typeof text === 'string'`,
  // because onNewTodo() is public, anyone can call onNewTodo(undefined) instead.
  text = text.toUpperCase()

  // ✅ We ensure `text` is a string
  if (typeof text !== 'string') {
    throw Abort()
  }
  text = text.toUpperCase()
}
```

For more convenience, we can use `shield()` instead:

```js
// CreateTodo.telefunc.js
// Environment: server

import { shield } from 'telefunc'
const t = shield.type

shield(onNewTodo, [t.string])
export async function onNewTodo(text) {
  // ...
}
```

```js
// CreateTodo.telefunc.js
// Environment: server

import { shield } from 'telefunc'
const t = shield.type

shield(onNewTodo, [{ text: t.string, isCompleted: t.boolean }])
export async function onNewTodo({ text, isCompleted }) {
  // ...
}
```

Not only does `shield()` call `throw Abort()` on our behalf, but it also infers the type of the arguments for TypeScript and IntelliSense.

<Link href="/shield#typescript-automatic">When using TypeScript, Telefunc can automatically generate `shield()` for each telefunction.</Link>


## Random telefunction calls

Any of your telefunctions can be called by anyone, at any time, and with any arguments. One way to think about it is that any random telefunction call can happen at any time.

You should always protect your telefunctions, even when your frontend calls a telefunction only in a certain way. For example:

```jsx
// Comment.jsx
// Environment: client

import { onDelete } from './Comment.telefunc.js'

function Comment({ id, text }) {
  const deleteButton =
    // The delete button is only shown to admins
    !user.isAdmin ? null : <button onClick={() => onDelete(id)}>Delete</button>
  return (
    <>
      <p>{text}</p>
      {deleteButton}
    </>
  )
}
```

Because the frontend shows the delete button only to admins, we can assume the user to be an admin whenever the frontend calls `onDelete()`.
But we still need to use `throw Abort()` in order to protect the telefunction against calls that don't originate from the frontend.

```js
// Comment.telefunc.js
// Environment: server

import { getContext, Abort, shield } from 'telefunc'

shield(onDelete, [shield.type.number])
export async function onDelete(id) {
  const { user } = getContext()

  // onDelete() is public and anyone can call it without being an admin.
  // We must abort if that happens.
  if (!user.isAdmin) throw Abort()

  // ...
}
```

## Performance

When used correctly, RPC is highly efficient.

Since telefunctions are tailored to your frontend, you can make them send only the strict minimum amount of data.

For example, the following doesn't send a single byte of superfluous information:

```js
// TodoList.telefunc.js
// Environment: server

import { getContext } from 'telefunc'

shield(onTodoComplete, [shield.type.number])
export async function onTodoComplete(id) {
  const { user } = getContext()
  await Task.update({ completed: true }).where({ id, author: user.id })
  // No data is returned: the HTTP response status code 200 is enough information for the client
}
```

> With REST, because API endpoints are generic you often end up returning superfluous data.

> Even with GraphQL, the client must specify and send the list of fields that the server needs to return. This isn't needed with RPC — that information is implicitly defined by the telefunction name (i.e. the information is compressed).

For efficient telefunctions, we recommend creating tailored telefunctions instead of generic ones — see <Link href="/event-based" />.

> Telefunc newcomers often make the mistake of creating generic telefunctions — a habit that comes from using REST or GraphQL.
